/*
 * Copyright (C) 2007 Eskil Bylund
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

using System;
using System.Collections;
using System.Collections.Generic;

using DCSharp.Backend.Objects;
using DCSharp.Hashing;

namespace DCSharp.Backend.Managers
{
	/// <summary>
	/// Decides what segments should be used when downloading a file.
	/// </summary>
	public interface ISegmentStrategy
	{
		Segment CreateSegment(SegmentedFileInfo info);
		int GetAvailableSegments(SegmentedFileInfo info);		
	}

	/// <summary>
	/// Helper base class for implementations of <see cref="ISegmentStrategy"/>.
	/// </summary>
	public abstract class SegmentStrategy : ISegmentStrategy
	{
		public abstract Segment CreateSegment(SegmentedFileInfo info);
		public abstract int GetAvailableSegments(SegmentedFileInfo info);

		/// <summary>
		/// Creates a new <see cref="Segment"/> from specific leaves.
		/// </summary>
		/// <param name="info">The <see cref="SegmentedFileInfo"/>.</param>
		/// <param name="startLeaf">Tbe start leaf.</param>
		/// <param name="endLeaf">The end leaf.</param>
		/// <returns>A new <see cref="Segment"/> instance.</returns>
		protected Segment CreateSegment(SegmentedFileInfo info, long startLeaf,
			long endLeaf)
		{
			HashTree hashTree = info.HashTree;
			if (startLeaf < 0 || startLeaf > hashTree.LeafCount)
			{
				throw new ArgumentOutOfRangeException("startLeaf");
			}
			if (endLeaf < startLeaf || endLeaf > hashTree.LeafCount)
			{
				throw new ArgumentOutOfRangeException("endLeaf");
			}
			long start = startLeaf * hashTree.BlockSize;
			long end = (endLeaf + 1) * hashTree.BlockSize;
			if (end > hashTree.FileSize)
			{
				end = hashTree.FileSize;
			}
			return new Segment(start, end);
		}

		/// <summary>
		/// Enumerates over the parts of the file that can be downloaded.
		/// </summary>
		/// <remarks>
		/// The key of the pair is the first available leaf, the value is the
		/// last. The first and last leaves will point to the same leaf if
		/// there's only one leaf available in a part.
		/// </remarks>
		/// <param name="info">The <see cref="SegmentedFileInfo"/>.</param>
		/// <returns>A <see cref="IEnumerable`1"/>.</returns>
		protected IEnumerable<KeyValuePair<int, int>> AvailableFileParts(
			SegmentedFileInfo info)
		{
			int left = -1;
			int right = -1;
			for (int i = 0; i < info.Completed.Length; i++)
			{
				bool available = !info.Completed[i] && !info.Active[i];
				if (available && left == -1)
				{
					left = right = i;
				}
				else if (left != -1 && available)
				{
					right = i;
				}
				else if (left != -1 && !available)
				{
					yield return new KeyValuePair<int, int>(left, right);
					left = right = -1;
				}
			}
			if (left != -1)
			{
				yield return new KeyValuePair<int, int>(left, right);
			}
		}
	}

	/// <summary>
	/// Creates a new segment for the first available part of the file, but only
	/// if no other active segment exists.
	/// </summary>
	public class SingleSegmentStrategy : SegmentStrategy
	{
		public override Segment CreateSegment(SegmentedFileInfo info)
		{
			if (HasActiveSegment(info))
			{
				return null;
			}
			foreach (KeyValuePair<int, int> pair in AvailableFileParts(info))
			{
				return CreateSegment(info, pair.Key, pair.Value);
			}
			return null;
		}

		public override int GetAvailableSegments(SegmentedFileInfo info)
		{
			if (HasActiveSegment(info))
			{
				return 0;
			}
			foreach (KeyValuePair<int, int> pair in AvailableFileParts(info))
			{
				return 1;
			}
			return 0;
		}

		private bool HasActiveSegment(SegmentedFileInfo info)
		{
			for (int i = 0; i < info.Active.Count; i++)
			{
				if (info.Active[i])
				{
					return true;
				}
			}
			return false;
		}
	}

	/// <summary>
	/// Creates segments no longer than a specific amount of leaves.
	/// </summary>
	public class MultiSegmentStrategy : SegmentStrategy
	{
		public override Segment CreateSegment(SegmentedFileInfo info)
		{
			int maxLeaves = GetMaxLeavesPerSegment(info);
			foreach (KeyValuePair<int, int> pair in AvailableFileParts(info))
			{
				int leaves = pair.Value - pair.Key + 1;
				return CreateSegment(info, pair.Key,
					pair.Key + Math.Min(leaves, maxLeaves) - 1);
			}
			return null;
		}

		public override int GetAvailableSegments(SegmentedFileInfo info)
		{
			int count = 0;
			int maxLeaves = GetMaxLeavesPerSegment(info);			
			foreach (KeyValuePair<int, int> pair in AvailableFileParts(info))
			{
				int leaves = pair.Value - pair.Key + 1;
				count += (int)((leaves + maxLeaves - 1) / maxLeaves);
			}
			return count;
		}

		protected virtual int GetMaxLeavesPerSegment(SegmentedFileInfo info)
		{
			// TODO: What's the best way to split a file? Using a fixed amount of
			// segments, using segments at a specific size or some other way?
			return 50;
		}
	}
}
